home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / typematic < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  32.2 KB  |  878 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) typematic.gawk 1.1 96/09/16
  4. # 94/02/14 John H. DuBois III (john@armory.com)
  5. # 96/09/16 Cleaned up.  Automatically create /dev/iob if it does not exist.
  6.  
  7. # Note: this program can be run with awk instead of gawk by swapping the first
  8. # two lines (so the first one is '#!/usr/bin/awk -f').  
  9. # In that case all option letters must be preceded by '+' instead of '-'.
  10.  
  11. # This program sets the typematic (autorepeat) rate of the PC/AT keyboard
  12. # controller.  This is done by writing a command & value to the data port
  13. # of the keyboard controller, using the access to the IO space provided by the
  14. # the SCO UNIX mm driver.
  15. # They keyboard controller is programmed as follows:
  16. # Command port address: 0x64 (out)
  17. # Status port address:  0x64 (in)
  18. # Data port address:    0x60 (in/out)
  19. # Commands may be written at any time.
  20. # Status may be read at any time.
  21. # Bit 0 of status is "output buffer full".
  22. # Data may be written only when this bit is 0.
  23. # Command byte 0xF3 is set typematic rate/delay.
  24. # *NOTE* Even though it's a "command byte" it goes to the data port.
  25. # After 0xF3 is written to the data port, a byte containing the delay and
  26. # repeat rate values is written.  The bits of this byte have these meanings:
  27. # 0ddeemmm   0 = always 0   d = delay   e = rate exponent   m = rate mantissa
  28. # 7: always 0
  29. # 5-6: Delay, 250..1000 mS.  Delay = (n + 1) * 250 mS +/- 20%
  30. # 4-0: Typematic rate.  Rate is 1/period.  Period = (A+8)*(2^B)*4.17mS
  31. #      B = bits 4-3, A = bits 2-0.
  32. # Thus the range of rates is: 1/(15*8*4.17mS) = 2  through  1/(8*4.17mS) = 30
  33. # Repeat clock: 1/4.17mS = 240 Hz
  34.  
  35. BEGIN {
  36.     Name = "typematic"
  37.     Usage = "Usage: " Name " [-htx] [<delay> <rate>]"
  38.     Clock = 240
  39.     ARGC = Opts(Name,Usage,"htx",0)
  40.     if ("h" in Options)
  41.     help()
  42.     if ("t" in Options)
  43.     table()
  44.     if ("t" in Options || "h" in Options)
  45.     exit(0)
  46.     Verbose = "x" in Options
  47.     if (ARGC < 2) {
  48.     print Usage | "cat 1>&2"
  49.     print "Usage -h for help." | "cat 1>&2"
  50.     exit 1
  51.     }
  52.  
  53.     for (Delay = 250; Delay <= 1000; Delay += 250)
  54.     Delays[Delay]
  55.  
  56.     DelayValue = RateValue = -1
  57.     for (argnum = 1; argnum < ARGC; argnum++) {
  58.     arg = ARGV[argnum]
  59.     if (arg in Delays)
  60.         DelayValue = MakeDelay(arg)
  61.     else if (arg ~ /^[0-9]*.?[0-9]+$/ && (2 <= arg && arg <= 30))
  62.         RateValue = MakeRate(arg)
  63.     else {
  64.         printf("Error: invalid argument '%s'\n%s\n",arg,Usage) | "cat 1>&2"
  65.         exit 1
  66.     }
  67.     }
  68.     if (RateValue == -1 || DelayValue == -1) {
  69.     print "Bad value given for delay and/or rate." | "cat 1>&2"
  70.     exit 1
  71.     }
  72.     WriteTypematicRegister(DelayValue + RateValue)
  73.     exit(0)
  74. }
  75.  
  76. function table(  format,Mantissa,Exponent,Gutter,Header) {
  77.     Gutter = 10
  78.     Header = \
  79.     sprintf("%5s   %4s %3s %3s %6s","rate","mant","exp","div","period")
  80.     printf "%s%" Gutter "s%s\n",Header,"",Header
  81.     format = "%5.2f   %4d %3d %3d %6.3f"
  82.     for (Exponent = 3; Exponent >= 2; Exponent--)
  83.     for (Mantissa = 15; Mantissa >= 8; Mantissa--) {
  84.         for (Col = 0; Col <= 2; Col += 2) {
  85.         Divisor = int(Mantissa * (2 ^ (Exponent - Col)) + 0.5)
  86.         Rate = Clock / Divisor
  87.         printf format,Rate,Mantissa,(Exponent - Col),Divisor,1 / Rate
  88.         if (Col)
  89.             print ""
  90.         else
  91.             printf "%" Gutter "s",""
  92.         }
  93.     }
  94. }
  95.  
  96. function MakeRate(Value,  Div,Mant,Exp,Divisor,Rate) {
  97.     Div = 240 / Value
  98.  
  99.     # Normalize
  100.     Mant = Div
  101.     Exp = 0
  102.     while (Mant > 15) {
  103.     Mant = int(Mant / 2 + 0.5)
  104.     Exp++
  105.     }
  106.  
  107.     if (Verbose) {
  108.     Divisor = int(Mant * (2 ^ Exp) + 0.5)
  109.     Rate = Clock / Divisor
  110.     printf "Setting rate to %.2f characters/second\n",Rate
  111.     printf "Divisor = %d, mantissa = %d, exponent = %d\n",
  112.     Divisor,Mant,Exp
  113.     }
  114.     return Mant - 8 + Exp * 8
  115. }
  116.  
  117. function MakeDelay(DecVal,  Value) {
  118.     Value = DecVal / 250 - 1
  119.     if (Verbose)
  120.     printf "Setting delay to %d mS; value is %d\n",DecVal,Value
  121.     return Value * 32
  122. }
  123.  
  124. function inb(Addr,  Cmd) {
  125.     hex2dec = "123456789abcdef"
  126.     Cmd = "dd if=/dev/iob bs=1 iseek=" addr " count=1 2>/dev/null | hd; exit 0"
  127.     Cmd | getline
  128.     close(Cmd)
  129.     return index(hex2dec,substr($2,1,1))*16 + index(hex2dec,substr($2,2,1))
  130. }
  131.  
  132. function outb(Addr,Value,  Cmd,Dev) {
  133.     Dev = "/dev/iob"
  134.     # awk won't write a null char; use echo
  135.     Cmd = sprintf(\
  136.     "if [ ! -c %s ]; then umask 077;mknod %s c 4 3 || exit 1; fi\n"\
  137.     "echo -n \"\\0%o\" | dd of=%s bs=1 oseek=%d count=1 2>/dev/null",
  138.     Dev,Dev,Value,Dev,Addr)
  139.     if (Verbose)
  140.     printf("Writing 0x%x to io address 0x%x with command:\n%s\n",
  141.     Value,Addr,Cmd)
  142.     system(Cmd)
  143.     if (Verbose)
  144.     print "Done."
  145. }
  146.  
  147. function WriteTypematicRegister(RegVal) {
  148.     DataPort = 96    # 0x60
  149. #    CommandPort = 100    # 0x64
  150.     SetTypematic = 243    # 0xf3
  151.     StatusPort = 100    # 0x64
  152.     outb(DataPort,SetTypematic)
  153. # This shouldn't be (and isn't) neccessary; just invoking dd twice to
  154. # write output will give vastly more than enough delay between outb's.
  155. #    StatusWait(StatusPort)
  156.     outb(DataPort,RegVal)
  157. }
  158.  
  159. function StatusWait(StatusPort,  Status,Count,ret,Tries) {
  160.     Count = Tries = 10
  161.     # Supposed to wait until bit 0 of status port is 0
  162.     while ((ret = (inb(StatusPort) % 2)) && Count)
  163.     Count--
  164.     if (ret) {
  165.     printf "Tried to write data port %d times; still busy.  Aborting.\n",
  166.     Tries
  167.     exit(1)
  168.     }
  169. }
  170.  
  171. function help(  Message) {
  172.     Message = \
  173. "typematic: set AT keyboard typematic (autorepeat) rate.\n"\
  174. Usage "\n" \
  175. "<delay> is the delay before keyboard repeat begins, in milliseconds.\n"\
  176. "It must be 250, 500, 750, or 1000.\n"\
  177. "<rate> is the repeat rate, in characters per second.  It must be between 2\n"\
  178. "and 30, and may include a decimal fraction.\n"\
  179. "Both the delay and rate must be given.\n"\
  180. "The order they are given in does not matter.\n"\
  181. "Options:\n"\
  182. "-h: Print this help.\n"\
  183. "-x: Print the exact value selected for the repeat rate, along with the\n"\
  184. "    actions taken to set the hardware.  There are 32 possible repeat\n"\
  185. "    rates; typematic selects the closest one.\n"\
  186. "-t: Print a table of the possible repeat rates (and intercharacter pause\n"\
  187. "    period), and the associated keyboard controller hardware parameters:\n"\
  188. "    the mantissa and exponent that would be written to the keyboard\n"\
  189. "    controller to program it for the desired repeat rate, and the\n"\
  190. "    resulting keyboard autorepeat divisor."
  191.     print Message
  192. }
  193.  
  194. ### Start of ProcArgs library
  195. # @(#) ProcArgs 1.11 96/12/08
  196. # 92/02/29 john h. dubois iii (john@armory.com)
  197. # 93/07/18 Added "#" arg type
  198. # 93/09/26 Do not count -h against MinArgs
  199. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  200. #          Removed meaning of "+" or "-" by itself.
  201. # 94/03/08 Added & option and *()< option types.
  202. # 94/04/02 Added NoRCopt to Opts()
  203. # 94/06/11 Mark numeric variables as such.
  204. # 94/07/08 Opts(): Do not require any args if h option is given.
  205. # 95/01/22 Record options given more than once.  Record option num in argv.
  206. # 95/06/08 Added ExclusiveOptions().
  207. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  208. #          Expand $VARNAME at the start of its filenames.
  209. #          Let varname=0 and -option- turn off an option.
  210. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  211. #          of the vars should be searched for in the environment.
  212. #          Check for duplicate rcfiles.
  213. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  214. #          now return various negatives values on error, not just -1, and
  215. #          Opts() may set Err to various positive values, not just 1.
  216. #          Added AllowUnrecOpt.
  217. # 96/05/23 Check type given for & option
  218. # 96/06/15 Re-port to awk
  219. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  220. #          used by other functions.
  221. # 96/10/15 Added OptChars
  222. # 96/11/01 Added exOpts arg to Opts()
  223. # 96/11/16 Added ; type
  224. # 96/12/08 Added Opt2Set() & Opt2Sets()
  225. # 96/12/27 Added CmdLineOpt()
  226.  
  227. # optlist is a string which contains all of the possible command line options.
  228. # A character followed by certain characters indicates that the option takes
  229. # an argument, with type as follows:
  230. # :    String argument
  231. # ;    Non-empty string argument
  232. # *    Floating point argument
  233. # (    Non-negative floating point argument
  234. # )    Positive floating point argument
  235. # #    Integer argument
  236. # <    Non-negative integer argument
  237. # >    Positive integer argument
  238. # The only difference the type of argument makes is in the runtime argument
  239. # error checking that is done.
  240.  
  241. # The & option is a special case used to get numeric options without the
  242. # user having to give an option character.  It is shorthand for [-+.0-9].
  243. # If & is included in optlist and an option string that begins with one of
  244. # these characters is seen, the value given to "&" will include the first
  245. # char of the option.  & must be followed by a type character other than ":"
  246. # or ";".
  247. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  248.  
  249. # Strings in argv[] which begin with "-" or "+" are taken to be
  250. # strings of options, except that a string which consists solely of "-"
  251. # or "+" is taken to be a non-option string; like other non-option strings,
  252. # it stops the scanning of argv and is left in argv[].
  253. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  254. # If an option takes an argument, the argument may either immediately
  255. # follow it or be given separately.
  256. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  257. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  258. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  259. # this feature had a flaw that caused problems in some cases.  See the OptChars
  260. # parameter to explicitly set the option-specifier characters.
  261.  
  262. # If an option that does not take an argument is given,
  263. # an index with its name is created in Options and its value is set to the
  264. # number of times it occurs in argv[].
  265.  
  266. # If an option that does take an argument is given, an index with its name is
  267. # created in Options and its value is set to the value of the argument given
  268. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  269. # If an option that takes an argument is given more than once,
  270. # Options[option-name,"count"] is incremented, and the value is assigned to
  271. # the index (option-name,instance) where instance is 2 for the second occurance
  272. # of the option, etc.
  273. # In other words, the first time an option with a value is encountered, the
  274. # value is assigned to an index consisting only of its name; for any further
  275. # occurances of the option, the value index has an extra (count) dimension.
  276.  
  277. # The sequence number for each option found in argv[] is stored in
  278. # Options[option-name,"num",instance], where instance is 1 for the first
  279. # occurance of the option, etc.  The sequence number starts at 1 and is
  280. # incremented for each option, both those that have a value and those that
  281. # do not.  Options set from a config file have a value of 0 assigned to this.
  282.  
  283. # Options and their arguments are deleted from argv.
  284. # Note that this means that there may be gaps left in the indices of argv[].
  285. # If compress is nonzero, argv[] is packed by moving its elements so that
  286. # they have contiguous integer indices starting with 0.
  287. # Option processing will stop with the first unrecognized option, just as
  288. # though -- was given except that unlike -- the unrecognized option will not be
  289. # removed from ARGV[].  Normally, an error value is returned in this case.
  290. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  291. # be found, so the number of remaining arguments is returned instead.
  292. # If OptChars is not a null string, it is the set of characters that indicate
  293. # that an argument is an option string if the string begins with one of the
  294. # characters.  A string consisting solely of two of the same option-indicator
  295. # characters stops the scanning of argv[].  The default is "-+".
  296. # argv[0] is not examined.
  297. # The number of arguments left in argc is returned.
  298. # If an error occurs, the global string OptErr is set to an error message
  299. # and a negative value is returned.
  300. # Current error values:
  301. # -1: option that required an argument did not get it.
  302. # -2: argument of incorrect type supplied for an option.
  303. # -3: unrecognized (invalid) option.
  304. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  305. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  306. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  307. {
  308. # ArgNum is the index of the argument being processed.
  309. # ArgsLeft is the number of arguments left in argv.
  310. # Arg is the argument being processed.
  311. # ArgLen is the length of the argument being processed.
  312. # ArgInd is the position of the character in Arg being processed.
  313. # Option is the character in Arg being processed.
  314. # Pos is the position in OptList of the option being processed.
  315. # NumOpt is true if a numeric option may be given.
  316.     ArgsLeft = argc
  317.     NumOpt = index(OptList,"&")
  318.     OptionNum = 0
  319.     if (OptChars == "")
  320.     OptChars = "-+"
  321.     while (OptChars != "") {
  322.     c = substr(OptChars,1,1)
  323.     OptChars = substr(OptChars,2)
  324.     OptCharSet[c]
  325.     OptTerm[c c]
  326.     }
  327.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  328.     Arg = argv[ArgNum]
  329.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  330.         break    # Not an option; quit
  331.     if (Arg in OptTerm) {
  332.         delete argv[ArgNum]
  333.         ArgsLeft--
  334.         break
  335.     }
  336.     ArgLen = length(Arg)
  337.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  338.         Option = substr(Arg,ArgInd,1)
  339.         if (NumOpt && Option ~ /[-+.0-9]/) {
  340.         # If this option is a numeric option, make its flag be & and
  341.         # its option string flag position be the position of & in
  342.         # the option string.
  343.         Option = "&"
  344.         Pos = NumOpt
  345.         # Prefix Arg with a char so that ArgInd will point to the
  346.         # first char of the numeric option.
  347.         Arg = "&" Arg
  348.         ArgLen++
  349.         }
  350.         # Find position of flag in option string, to get its type (if any).
  351.         # Disallow & as literal flag.
  352.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  353.         if (AllowUnrecOpt) {
  354.             Escape = 1
  355.             break
  356.         }
  357.         else {
  358.             OptErr = "Invalid option: " specGiven Option
  359.             return -3
  360.         }
  361.         }
  362.  
  363.         # Find what the value of the option will be if it takes one.
  364.         # NeedNextOpt is true if the option specifier is the last char of
  365.         # this arg, which means that if the option requires a value it is
  366.         # the next arg.
  367.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  368.         if (GotValue = ArgNum + 1 < argc)
  369.             Value = argv[ArgNum+1]
  370.         }
  371.         else {    # Value is included with option
  372.         Value = substr(Arg,ArgInd + 1)
  373.         GotValue = 1
  374.         }
  375.  
  376.         if (HadValue = AssignVal(Option,Value,Options,
  377.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  378.         specGiven)) {
  379.         if (HadValue < 0)    # error occured
  380.             return HadValue
  381.         if (HadValue == 2)
  382.             ArgInd++    # Account for the single-char value we used.
  383.         else {
  384.             if (NeedNextOpt) {    # option took next arg as value
  385.             delete argv[++ArgNum]
  386.             ArgsLeft--
  387.             }
  388.             break    # This option has been used up
  389.         }
  390.         }
  391.     }
  392.     if (Escape)
  393.         break
  394.     # Do not delete arg until after processing of it, so that if it is not
  395.     # recognized it can be left in ARGV[].
  396.     delete argv[ArgNum]
  397.     ArgsLeft--
  398.     }
  399.     if (compress != 0) {
  400.     dest = 1
  401.     src = argc - ArgsLeft + 1
  402.     for (count = ArgsLeft - 1; count; count--) {
  403.         ARGV[dest] = ARGV[src]
  404.         dest++
  405.         src++
  406.     }
  407.     }
  408.     return ArgsLeft
  409. }
  410.  
  411. # Assignment to values in Options[] occurs only in this function.
  412. # Option: Option specifier character.
  413. # Value: Value to be assigned to option, if it takes a value.
  414. # Options[]: Options array to return values in.
  415. # ArgType: Argument type specifier character.
  416. # GotValue: Whether any value is available to be assigned to this option.
  417. # Name: Name of option being processed.
  418. # OptionNum: Number of this option (starting with 1) if set in argv[],
  419. #     or 0 if it was given in a config file or in the environment.
  420. # SingleOpt: true if the value (if any) that is available for this option was
  421. #     given as part of the same command line arg as the option.  Used only for
  422. #     options from the command line.
  423. # specGiven is the option specifier character use, if any (e.g. - or +),
  424. # for use in error messages.
  425. # Global variables: OptErr
  426. # Return value: negative value on error, 0 if option did not require an
  427. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  428. # the arg.
  429. # Current error values:
  430. # -1: Option that required an argument did not get it.
  431. # -2: Value of incorrect type supplied for option.
  432. # -3: Bad type given for option &
  433. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  434. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  435.     # If option takes a value...    [
  436.     NumTypes = "*()#<>]"
  437.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  438.     OptErr = "Bad type given for & option"
  439.     return -3
  440.     }
  441.  
  442.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  443.     if (!GotValue) {
  444.         if (Name != "")
  445.         OptErr = "Variable requires a value -- " Name
  446.         else
  447.         OptErr = "option requires an argument -- " Option
  448.         return -1
  449.     }
  450.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  451.         OptErr = Err
  452.         return -2
  453.     }
  454.     # Mark this as a numeric variable; will be propogated to Options[] val.
  455.     if (ArgType != ":" && ArgType != ";")
  456.         Value += 0
  457.     if ((Instance = ++Options[Option,"count"]) > 1)
  458.         Options[Option,Instance] = Value
  459.     else
  460.         Options[Option] = Value
  461.     }
  462.     # If this is an environ or rcfile assignment & it was given a value...
  463.     else if (!OptionNum && Value != "") {
  464.     UsedValue = 1
  465.     # If the value is "0" or "-" and this is the first instance of it,
  466.     # do not set Options[Option]; this allows an assignment in an rcfile to
  467.     # turn off an option (for the simple "Option in Options" test) in such
  468.     # a way that it cannot be turned on in a later file.
  469.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  470.         Instance = 1
  471.     else
  472.         Instance = ++Options[Option]
  473.     # Save the value even though this is a flag
  474.     Options[Option,Instance] = Value
  475.     }
  476.     # If this is a command line flag and has a - following it in the same arg,
  477.     # it is being turned off.
  478.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  479.     UsedValue = 2
  480.     if (Option in Options)
  481.         Instance = ++Options[Option]
  482.     else
  483.         Instance = 1
  484.     Options[Option,Instance]
  485.     }
  486.     # If this is a flag assignment without a value, increment the count for the
  487.     # flag unless it was turned off.  The indicator for a flag being turned off
  488.     # is that the flag index has not been set in Options[] but it has an
  489.     # instance count.
  490.     else if (Option in Options || !((Option,1) in Options))
  491.     # Increment number of times this flag seen; will inc null value to 1
  492.     Instance = ++Options[Option]
  493.     Options[Option,"num",Instance] = OptionNum
  494.     return UsedValue
  495. }
  496.  
  497. # Option is the option letter
  498. # Value is the value being assigned
  499. # Name is the var name of the option, if any
  500. # ArgType is one of:
  501. # :    String argument
  502. # ;    Non-null string argument
  503. # *    Floating point argument
  504. # (    Non-negative floating point argument
  505. # )    Positive floating point argument
  506. # #    Integer argument
  507. # <    Non-negative integer argument
  508. # >    Positive integer argument
  509. # specGiven is the option specifier character use, if any (e.g. - or +),
  510. # for use in error messages.
  511. # Returns null on success, err string on error
  512. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  513.     if (ArgType == ":")
  514.     return ""
  515.     if (ArgType == ";") {
  516.     if (Value == "")
  517.         Err = "must be a non-empty string"
  518.     }
  519.     # A number begins with optional + or -, and is followed by a string of
  520.     # digits or a decimal with digits before it, after it, or both
  521.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  522.     Err = "must be a number"
  523.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  524.     Err = "may not include a fraction"
  525.     else if (ArgType ~ "[()<>]" && Value < 0)
  526.     Err = "may not be negative"
  527.     # (
  528.     else if (ArgType ~ "[)>]" && Value == 0)
  529.     Err = "must be a positive number"
  530.     if (Err != "") {
  531.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  532.     if (Name != "")
  533.         return ErrStr "variable " substr(Name,1,1) " " Err
  534.     else {
  535.         if (Option == "&")
  536.         Option = Value
  537.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  538.     }
  539.     }
  540.     else
  541.     return ""
  542. }
  543.  
  544. # Note: only the above functions are needed by ProcArgs.
  545. # The rest of these functions call ProcArgs() and also do other
  546. # option-processing stuff.
  547.  
  548. # Opts: Process command line arguments.
  549. # Opts processes command line arguments using ProcArgs()
  550. # and checks for errors.  If an error occurs, a message is printed
  551. # and the program is exited.
  552. #
  553. # Input variables:
  554. # Name is the name of the program, for error messages.
  555. # Usage is a usage message, for error messages.
  556. # OptList the option description string, as used by ProcArgs().
  557. # MinArgs is the minimum number of non-option arguments that this
  558. # program should have, non including ARGV[0] and +h.
  559. # If the program does not require any non-option arguments,
  560. # MinArgs should be omitted or given as 0.
  561. # rcFiles, if given, is a colon-seprated list of filenames to read for
  562. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  563. # by the value of the environment variable HOME.  If a filename begins with
  564. # $, the part from the character after the $ up until (but not including)
  565. # the first character not in [a-zA-Z0-9_] will be searched for in the
  566. # environment; if found its value will be substituted, if not the filename will
  567. # be discarded.
  568. # rcfiles are read in the order given.
  569. # Values given in them will not override values given on the command line,
  570. # and values given in later files will not override those set in earlier
  571. # files, because AssignVal() will store each with a different instance index.
  572. # The first instance of each variable, either on the command line or in an
  573. # rcfile, will be stored with no instance index, and this is the value
  574. # normally used by programs that call this function.
  575. # VarNames is a comma-separated list of variable names to map to options,
  576. # in the same order as the options are given in OptList.
  577. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  578. # searched for in the environment.  If set to -1, all values will be searched
  579. # for in the environment.  Values given in the environment will override
  580. # those given in the rcfiles but not those given on the command line.
  581. # NoRCopt, if given, is an additional letter option that if given on the
  582. # command line prevents the rcfiles from being read.
  583. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  584. # ExclusiveOptions() for a description of exOpts.
  585. # Special options:
  586. # If x is made an option and is given, some debugging info is output.
  587. # h is assumed to be the help option.
  588.  
  589. # Global variables:
  590. # The command line arguments are taken from ARGV[].
  591. # The arguments that are option specifiers and values are removed from
  592. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  593. # The number of elements in ARGV[] should be in ARGC.
  594. # After processing, ARGC is set to the number of elements left in ARGV[].
  595. # The option values are put in Options[].
  596. # On error, Err is set to a positive integer value so it can be checked for in
  597. # an END block.
  598. # Return value: The number of elements left in ARGV is returned.
  599. # Must keep OptErr global since it may be set by InitOpts().
  600. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  601. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  602.     if (MinArgs == "")
  603.     MinArgs = 0
  604.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  605.     optChars)
  606.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  607.     if (ArgsLeft >= 0) {
  608.         OptErr = "Not enough arguments"
  609.         Err = 4
  610.     }
  611.     else
  612.         Err = -ArgsLeft
  613.     printf "%s: %s.\nUse -h for help.\n%s\n",
  614.     Name,OptErr,Usage > "/dev/stderr"
  615.     exit 1
  616.     }
  617.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  618.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  619.     {
  620.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  621.     Err = -e
  622.     exit 1
  623.     }
  624.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  625.     {
  626.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  627.     Err = 1
  628.     exit 1
  629.     }
  630.     return ArgsLeft
  631. }
  632.  
  633. # ReadConfFile(): Read a file containing var/value assignments, in the form
  634. # <variable-name><assignment-char><value>.
  635. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  636. # line and whitespace between the variable name and the assignment character) 
  637. # is stripped.  Lines that do not contain an assignment operator or which
  638. # contain a null variable name are ignored, other than possibly being noted in
  639. # the return value.  If more than one assignment is made to a variable, the
  640. # first assignment is used.
  641. # Input variables:
  642. # File is the file to read.
  643. # Comment is the line-comment character.  If it is found as the first non-
  644. #     whitespace character on a line, the line is ignored.
  645. # Assign is the assignment string.  The first instance of Assign on a line
  646. #     separates the variable name from its value.
  647. # If StripWhite is true, whitespace around the value (whitespace between the
  648. #     assignment char and trailing whitespace on the line) is stripped.
  649. # VarPat is a pattern that variable names must match.  
  650. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  651. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  652. #     a line; no assignment operator is needed.  These variables are set in
  653. #     the output array with a null value.  Lines containing nothing but
  654. #     whitespace are still ignored.
  655. # Output variables:
  656. # Values[] contains the assignments, with the indexes being the variable names
  657. #     and the values being the assigned values.
  658. # Lines[] contains the line number that each variable occured on.  A flag set
  659. #     is record by giving it an index in Lines[] but not in Values[].
  660. # Return value:
  661. # If any errors occur, a string consisting of descriptions of the errors
  662. # separated by newlines is returned.  In no case will the string start with a
  663. # numeric value.  If no errors occur,  the number of lines read is returned.
  664. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  665. FlagsOK,
  666. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  667.     if (Comment != "")
  668.     Comment = "^" Comment
  669.     AssignLen = length(Assign)
  670.     if (VarPat == "")
  671.     VarPat = "."    # null varname not allowed
  672.     while ((Status = (getline Line < File)) == 1) {
  673.     LineNum++
  674.     sub("^[ \t]+","",Line)
  675.     if (Line == "")        # blank line
  676.         continue
  677.     if (Comment != "" && Line ~ Comment)
  678.         continue
  679.     if (Pos = index(Line,Assign)) {
  680.         Var = substr(Line,1,Pos-1)
  681.         Val = substr(Line,Pos+AssignLen)
  682.         if (StripWhite) {
  683.         sub("^[ \t]+","",Val)
  684.         sub("[ \t]+$","",Val)
  685.         }
  686.     }
  687.     else {
  688.         Var = Line    # If no value, var is entire line
  689.         Val = ""
  690.     }
  691.     if (!FlagsOK && Val == "") {
  692.         Errs = Errs \
  693.         sprintf("\nBad assignment on line %d of file %s: %s",
  694.         LineNum,File,Line)
  695.         continue
  696.     }
  697.     sub("[ \t]+$","",Var)
  698.     if (Var !~ VarPat) {
  699.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  700.         LineNum,File,Var)
  701.         continue
  702.     }
  703.     if (!(Var in Lines)) {
  704.         Lines[Var] = LineNum
  705.         if (Pos)
  706.         Values[Var] = Val
  707.     }
  708.     }
  709.     if (Status)
  710.     Errs = Errs "\nCould not read file " File
  711.     close(File)
  712.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  713. }
  714.  
  715. # Variables:
  716. # Data is stored in Options[].
  717. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  718. # Global vars:
  719. # Sets OptErr.  Uses ENVIRON[].
  720. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  721. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  722. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  723. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  724.     split("",filesRead,"")    # make awk know this is an array
  725.     NumVars = split(VarNames,Vars,",")
  726.     TypesInd = Ret = 0
  727.     if (EnvSearch == -1)
  728.     EnvSearch = NumVars
  729.     for (i = 1; i <= NumVars; i++) {
  730.     Var = Vars[i]
  731.     CharOpt = substr(OptList,++TypesInd,1)
  732.     if (CharOpt ~ "^[:;*()#<>&]$")
  733.         CharOpt = substr(OptList,++TypesInd,1)
  734.     Map[Var] = CharOpt
  735.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  736.     # Do not overwrite entries from environment
  737.     if (i <= EnvSearch && Var in ENVIRON &&
  738.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  739.         return Err
  740.     }
  741.  
  742.     numrcFiles = split(rcFiles,fNames,":")
  743.     for (i = 1; i <= numrcFiles; i++) {
  744.     rcFile = fNames[i]
  745.     if (rcFile ~ "^~/")
  746.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  747.     else if (rcFile ~ /^\$/) {
  748.         rcFile = substr(rcFile,2)
  749.         match(rcFile,"^[a-zA-Z0-9_]*")
  750.         envvar = substr(rcFile,1,RLENGTH)
  751.         if (envvar in ENVIRON)
  752.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  753.         else
  754.         continue
  755.     }
  756.     if (rcFile in filesRead)
  757.         continue
  758.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  759.     # may be the same
  760.     filesRead[rcFile]
  761.     if ("x" in Options)
  762.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  763.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  764.     if (retStr > 0)
  765.         READ_RCFILE = 1
  766.     else if (ret != "") {
  767.         OptErr = retStr
  768.         Ret = -1
  769.     }
  770.     for (Var in Lines)
  771.         if (Var in Map) {
  772.         if ((Err = AssignVal(Map[Var],
  773.         Var in Values ? Values[Var] : "",Options,Types[Var],
  774.         Var in Values,Var,0)) < 0)
  775.             return Err
  776.         }
  777.         else {
  778.         OptErr = sprintf(\
  779.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  780.         Lines[Var],rcFile)
  781.         Ret = -1
  782.         }
  783.     }
  784.  
  785.     if ("x" in Options)
  786.     for (Var in Map)
  787.         if (Map[Var] in Options)
  788.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  789.         "/dev/stderr"
  790.         else
  791.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  792.     return Ret
  793. }
  794.  
  795. # OptSets is a semicolon-separated list of sets of option sets.
  796. # Within a list of option sets, the option sets are separated by commas.  For
  797. # each set of sets, if any option in one of the sets is in Options[] AND any
  798. # option in one of the other sets is in Options[], an error string is returned.
  799. # If no conflicts are found, nothing is returned.
  800. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  801. # the exclusions presented by the first set of sets (ab,def,g) if:
  802. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  803. # (a or b is in Options[]) AND (g is in Options) OR
  804. # (d, e, or f is in Options[]) AND (g is in Options)
  805. # An error will be returned due to the exclusions presented by the second set
  806. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  807. # todo: make options given on command line unset options given in config file
  808. # todo: that they conflict with.
  809. function ExclusiveOptions(OptSets,Options,
  810. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  811. SetNum,OSetNum) {
  812.     NumSetSets = split(OptSets,SetSets,";")
  813.     # For each set of sets...
  814.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  815.     # NumSets is the number of sets in this set of sets.
  816.     NumSets = split(SetSets[SetSet],Sets,",")
  817.     # For each set in a set of sets except the last...
  818.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  819.         s1 = Sets[SetNum]
  820.         L1 = length(s1)
  821.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  822.         # If any of the options in this set was given, check whether
  823.         # any of the options in the other sets was given.  Only check
  824.         # later sets since earlier sets will have already been checked
  825.         # against this set.
  826.         if ((c1 = substr(s1,Pos1,1)) in Options)
  827.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  828.             s2 = Sets[OSetNum]
  829.             L2 = length(s2)
  830.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  831.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  832.                 ErrStr = ErrStr "\n"\
  833.                 sprintf("Cannot give both %s and %s options.",
  834.                 c1,c2)
  835.             }
  836.     }
  837.     }
  838.     if (ErrStr != "")
  839.     return substr(ErrStr,2)
  840.     return ""
  841. }
  842.  
  843. # The value of each instance of option Opt that occurs in Options[] is made an
  844. # index of Set[].
  845. # The return value is the number of instances of Opt in Options.
  846. function Opt2Set(Options,Opt,Set,  count) {
  847.     if (!(Opt in Options))
  848.     return 0
  849.     Set[Options[Opt]]
  850.     count = Options[Opt,"count"]
  851.     for (; count > 1; count--)
  852.     Set[Options[Opt,count]]
  853.     return count
  854. }
  855.  
  856. # The value of each instance of option Opt that occurs in Options[] that
  857. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  858. # Other values are made indexes of Set[].
  859. # The return value is the number of instances of Opt in Options.
  860. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  861.     ret = Opt2Set(Options,Opt,aSet)
  862.     for (value in aSet)
  863.     if (substr(value,1,1) == "!")
  864.         nSet[substr(value,2)]
  865.     else
  866.         Set[value]
  867.     return ret
  868. }
  869.  
  870. # Returns true if option Opt was given on the command line.
  871. function CmdLineOpt(Options,Opt,  i) {
  872.     for (i = 1; (Opt,"num",i) in Options; i++)
  873.     if (Options[Opt,"num",i] != 0)
  874.         return 1
  875.     return 0
  876. }
  877. ### End of ProcArgs library
  878.